Українська

Вивчіть масштабовані патерни дизайну GraphQL-схем для створення надійних API для глобальної аудиторії. Опануйте зшивання схем, федерацію та модуляризацію.

Дизайн GraphQL схеми: масштабовані патерни для глобальних API

GraphQL став потужною альтернативою традиційним REST API, пропонуючи клієнтам гнучкість у запиті саме тих даних, які їм потрібні. Однак, у міру зростання складності та масштабу вашого GraphQL API – особливо при обслуговуванні глобальної аудиторії з різними вимогами до даних – ретельне проєктування схеми стає вирішальним для підтримки, масштабованості та продуктивності. Ця стаття розглядає кілька масштабованих патернів проєктування GraphQL-схем, щоб допомогти вам створювати надійні API, здатні впоратися з вимогами глобального застосунку.

Важливість масштабованого дизайну схеми

Добре спроєктована GraphQL-схема є основою успішного API. Вона визначає, як клієнти можуть взаємодіяти з вашими даними та сервісами. Поганий дизайн схеми може призвести до низки проблем, зокрема:

Для глобальних застосунків ці проблеми посилюються. Різні регіони можуть мати різні вимоги до даних, регуляторні обмеження та очікування щодо продуктивності. Масштабований дизайн схеми дозволяє ефективно вирішувати ці проблеми.

Ключові принципи масштабованого дизайну схеми

Перш ніж заглиблюватися в конкретні патерни, давайте окреслимо деякі ключові принципи, якими слід керуватися при проєктуванні схеми:

Патерни масштабованого дизайну схеми

Ось кілька патернів масштабованого дизайну схеми, які ви можете використовувати для створення надійних GraphQL API:

1. Зшивання схем (Schema Stitching)

Зшивання схем дозволяє об'єднувати кілька GraphQL API в одну єдину схему. Це особливо корисно, коли у вас є різні команди або сервіси, відповідальні за різні частини ваших даних. Це схоже на те, що у вас є кілька міні-API, які з'єднуються через шлюзовий API.

Як це працює:

  1. Кожна команда або сервіс надає власний GraphQL API зі своєю схемою.
  2. Центральний сервіс-шлюз використовує інструменти для зшивання схем (наприклад, Apollo Federation або GraphQL Mesh), щоб об'єднати ці схеми в одну єдину.
  3. Клієнти взаємодіють зі шлюзом, який направляє запити до відповідних базових API.

Приклад:

Уявіть собі платформу електронної комерції з окремими API для товарів, користувачів та замовлень. Кожен API має власну схему:

  
    # API товарів
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }

    # API користувачів
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    # API замовлень
    type Order {
      id: ID!
      userId: ID!
      productId: ID!
      quantity: Int!
    }

    type Query {
      order(id: ID!): Order
    }
  

Сервіс-шлюз може зшити ці схеми разом, щоб створити єдину схему:

  
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Order {
      id: ID!
      user: User! @relation(field: "userId")
      product: Product! @relation(field: "productId")
      quantity: Int!
    }

    type Query {
      product(id: ID!): Product
      user(id: ID!): User
      order(id: ID!): Order
    }
  

Зверніть увагу, як тип Order тепер містить посилання на User та Product, хоча ці типи визначені в окремих API. Це досягається за допомогою директив зшивання схем (як @relation у цьому прикладі).

Переваги:

Що варто врахувати:

2. Федерація схем (Schema Federation)

Федерація схем є еволюцією зшивання схем, розробленою для вирішення деяких її обмежень. Вона забезпечує більш декларативний та стандартизований підхід до композиції GraphQL-схем.

Як це працює:

  1. Кожен сервіс надає GraphQL API та анотує свою схему директивами федерації (наприклад, @key, @extends, @external).
  2. Центральний сервіс-шлюз (використовуючи Apollo Federation) використовує ці директиви для побудови суперграфа – представлення всієї федеративної схеми.
  3. Сервіс-шлюз використовує суперграф для маршрутизації запитів до відповідних базових сервісів та вирішення залежностей.

Приклад:

Використовуючи той самий приклад з електронною комерцією, федеративні схеми можуть виглядати так:

  
    # API товарів
    type Product @key(fields: "id") {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }

    # API користувачів
    type User @key(fields: "id") {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    # API замовлень
    type Order {
      id: ID!
      userId: ID!
      productId: ID!
      quantity: Int!
      user: User! @requires(fields: "userId")
      product: Product! @requires(fields: "productId")
    }

    extend type Query {
      order(id: ID!): Order
    }
  

Зверніть увагу на використання директив федерації:

Переваги:

Що варто врахувати:

3. Модульний дизайн схеми

Модульний дизайн схеми передбачає розбиття великої, монолітної схеми на менші, більш керовані модулі. Це полегшує розуміння, модифікацію та повторне використання окремих частин вашого API, навіть без використання федеративних схем.

Як це працює:

  1. Визначте логічні межі у вашій схемі (наприклад, користувачі, товари, замовлення).
  2. Створіть окремі модулі для кожної межі, визначаючи типи, запити та мутації, пов'язані з нею.
  3. Використовуйте механізми імпорту/експорту (залежно від реалізації вашого GraphQL-сервера) для об'єднання модулів в єдину схему.

Приклад (з використанням JavaScript/Node.js):

Створіть окремі файли для кожного модуля:

  
    // users.graphql
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type Query {
      user(id: ID!): User
    }

    // products.graphql
    type Product {
      id: ID!
      name: String!
      price: Float!
    }

    type Query {
      product(id: ID!): Product
    }
  

Потім об'єднайте їх у вашому головному файлі схеми:

  
    // schema.js
    const { makeExecutableSchema } = require('graphql-tools');
    const { typeDefs: userTypeDefs, resolvers: userResolvers } = require('./users');
    const { typeDefs: productTypeDefs, resolvers: productResolvers } = require('./products');

    const typeDefs = [
      userTypeDefs,
      productTypeDefs,
      ""
    ];

    const resolvers = {
      Query: {
        ...userResolvers.Query,
        ...productResolvers.Query,
      }
    };

    const schema = makeExecutableSchema({
      typeDefs,
      resolvers,
    });

    module.exports = schema;
  

Переваги:

Що варто врахувати:

4. Інтерфейси та об'єднання типів (Interface and Union Types)

Інтерфейси та об'єднання типів дозволяють визначати абстрактні типи, які можуть бути реалізовані кількома конкретними типами. Це корисно для представлення поліморфних даних – даних, які можуть набувати різних форм залежно від контексту.

Як це працює:

Приклад:

  
    interface Node {
      id: ID!
    }

    type User implements Node {
      id: ID!
      name: String!
      email: String!
    }

    type Product implements Node {
      id: ID!
      name: String!
      price: Float!
    }

    union SearchResult = User | Product

    type Query {
      node(id: ID!): Node
      search(query: String!): [SearchResult!]!
    }
  

У цьому прикладі і User, і Product реалізують інтерфейс Node, який визначає спільне поле id. Тип об'єднання SearchResult представляє результат пошуку, який може бути або User, або Product. Клієнти можуть запитувати поле `search` і потім використовувати поле `__typename`, щоб визначити, який тип результату вони отримали.

Переваги:

Що варто врахувати:

5. Патерн з'єднання (Connection Pattern)

Патерн з'єднання є стандартним способом реалізації пагінації в GraphQL API. Він забезпечує узгоджений та ефективний спосіб отримання великих списків даних частинами.

Як це працює:

Приклад:

  
    type User {
      id: ID!
      name: String!
      email: String!
    }

    type UserEdge {
      node: User!
      cursor: String!
    }

    type UserConnection {
      edges: [UserEdge!]!
      pageInfo: PageInfo!
    }

    type PageInfo {
      hasNextPage: Boolean!
      hasPreviousPage: Boolean!
      startCursor: String
      endCursor: String
    }

    type Query {
      users(first: Int, after: String, last: Int, before: String): UserConnection!
    }
  

Переваги:

Що варто врахувати:

Глобальні аспекти

При проєктуванні GraphQL-схеми для глобальної аудиторії враховуйте ці додаткові фактори:

Наприклад, розглянемо поле опису товару:


type Product {
 id: ID!
 name: String!
 description(language: String = "en"): String!
}

Це дозволяє клієнтам запитувати опис конкретною мовою. Якщо мову не вказано, за замовчуванням використовується англійська (`en`).

Висновок

Масштабований дизайн схеми є надзвичайно важливим для створення надійних і підтримуваних GraphQL API, здатних впоратися з вимогами глобального застосунку. Дотримуючись принципів, викладених у цій статті, та використовуючи відповідні патерни проєктування, ви можете створювати API, які легко розуміти, змінювати та розширювати, забезпечуючи при цьому відмінну продуктивність та масштабованість. Не забувайте про модуляризацію, композицію та абстракцію вашої схеми, а також враховуйте конкретні потреби вашої глобальної аудиторії.

Застосовуючи ці патерни, ви зможете розкрити весь потенціал GraphQL і створювати API, які будуть живити ваші застосунки протягом багатьох років.